#region Copyright Notice
// 
// 
//  
//  Copyright (c) 2009-2011 Anindya Chatterjee
//  
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//  
//      http://www.apache.org/licenses/LICENSE-2.0
//  
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
// 
// 
#endregion
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;

namespace AbstractClass.Collections.Generic
{
    /// <summary>
    /// Represents a strongly typed list of keys and values where a key can have multiple instances
    /// unlike <see cref="Dictionary{TKey,TValue}"/>.
    /// </summary>
    /// <typeparam name="TKey">The type of the key.</typeparam>
    /// <typeparam name="TValue">The type of the value.</typeparam>
    public class Map<TKey, TValue> : IDictionary<TKey, TValue>, IXmlSerializable
    {
        private readonly List<KeyValuePair<TKey, TValue>> _pairList;

        /// <summary>
        /// Initializes a new instance of the <see cref="Map&lt;TKey, TValue&gt;"/> class.
        /// </summary>
        public Map()
        {
            _pairList = new List<KeyValuePair<TKey, TValue>>();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Map&lt;TKey, TValue&gt;"/> class.
        /// </summary>
        /// <param name="capacity">The initial capacity of the collection.</param>
        public Map(int capacity)
        {
            _pairList = new List<KeyValuePair<TKey, TValue>>(capacity);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Map&lt;TKey, TValue&gt;"/> class.
        /// </summary>
        /// <param name="collection">The collection whose elements are copied to the new <see cref="Map&lt;TKey, TValue&gt;"/>.</param>
        public Map(IEnumerable<KeyValuePair<TKey, TValue>> collection)
        {
            _pairList = new List<KeyValuePair<TKey, TValue>>(collection);
        }

        /// <summary>
        /// Gets or sets the element at the specified index.
        /// </summary>
        /// <param name="index">The zero-based index of the element to get or set.</param>
        public KeyValuePair<TKey, TValue> this[int index]
        {
            get { return _pairList[index]; }
            set { _pairList[index] = value; }
        }

        #region IDictionary<TKey,TValue> Members

        /// <summary>
        ///   Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        ///   A <see cref = "IEnumerator&lt;T&gt;" /> that can be used to iterate through the collection.
        /// </returns>
        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            return _pairList.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        /// <summary>
        ///   Adds a <see cref="KeyValuePair&lt;TKey, TValue&gt;"/> item to the <see cref="Map&lt;TKey, TValue&gt;"/>.
        /// </summary>
        /// <param name = "item">The object to add to the <see cref="Map&lt;TKey, TValue&gt;"/>.
        /// </param>
        public void Add(KeyValuePair<TKey, TValue> item)
        {
            _pairList.Add(item);
        }

        /// <summary>
        /// Clears this instance by removing all items from it.
        /// </summary>
        public void Clear()
        {
            _pairList.Clear();
        }

        /// <summary>
        ///   Determines whether the <see cref="Map&lt;TKey, TValue&gt;"/> contains a specific item.
        /// </summary>
        /// <returns>
        ///   <c>true</c> if <paramref name="item" /> is found in the <see cref="Map&lt;TKey, TValue&gt;"/>; otherwise, <c>false</c>.
        /// </returns>
        /// <param name="item">The object to locate in the <see cref="Map&lt;TKey, TValue&gt;"/>.
        /// </param>
        public bool Contains(KeyValuePair<TKey, TValue> item)
        {
            return _pairList.Contains(item);
        }

        /// <summary>
        /// Copies the elements of the <see cref="Map&lt;TKey, TValue&gt;"/> to an array of 
        /// <see cref="KeyValuePair&lt;TKey, TValue&gt;"/>, starting at the specified array index.
        /// </summary>
        /// <param name="array">The one-dimensional array of type <see cref="KeyValuePair&lt;TKey, TValue&gt;"/> that is the destination of
        /// the elements copied from the <see cref="Map&lt;TKey, TValue&gt;"/>. The array must have zero-based indexing.
        ///	 </param>
        /// <param name="arrayIndex">The zero-based index in array at which copying begins.</param>
        /// <remarks>The nodes are fetched in In-Order fashion.</remarks>
        public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
        {
            _pairList.CopyTo(array, arrayIndex);
        }

        /// <summary>
        ///   Removes the first occurrence of a specific <see cref="KeyValuePair&lt;TKey, TValue&gt;"/> object 
        /// from the <see cref="Map&lt;TKey, TValue&gt;"/>.
        /// </summary>
        /// <param name = "item">The object to remove from the <see cref="Map&lt;TKey, TValue&gt;"/>.
        /// </param>
        /// <returns>
        ///   <c>true</c> if <paramref name = "item" /> was successfully removed from the <see cref="Map&lt;TKey, TValue&gt;"/>;
        /// 		otherwise, <c>false</c>. This method also returns <c>false</c> if <paramref name = "item" /> is not found in the original
        /// 		<see cref="Map&lt;TKey, TValue&gt;"/>.
        /// </returns>
        public bool Remove(KeyValuePair<TKey, TValue> item)
        {
            return _pairList.Remove(item);
        }

        /// <summary>
        ///   Gets the number of elements contained in the <see cref="Map&lt;TKey, TValue&gt;"/>.
        /// </summary>
        /// <returns>
        ///   The number of elements contained in the <see cref="Map&lt;TKey, TValue&gt;"/>.
        /// </returns>
        public int Count
        {
            get { return _pairList.Count; }
        }

        /// <summary>
        ///   Gets a value indicating whether the <see cref="Map&lt;TKey, TValue&gt;"/> is read-only.
        /// </summary>
        /// <returns>
        ///   true if the <see cref="Map&lt;TKey, TValue&gt;"/> is read-only; otherwise, false.
        /// </returns>
        public bool IsReadOnly
        {
            get { return false; }
        }

        /// <summary>
        ///   Determines whether the <see cref="Map&lt;TKey, TValue&gt;"/> contains a specific key.
        /// </summary>
        /// <returns>
        ///   <c>true</c> if <paramref name="key" /> is found in the <see cref="Map&lt;TKey, TValue&gt;"/>; otherwise, <c>false</c>.
        /// </returns>
        /// <param name="key">The key to locate in the <see cref="Map&lt;TKey, TValue&gt;"/>.
        /// </param>
        public bool ContainsKey(TKey key)
        {
            return _pairList.Any(valuePair => valuePair.Key.Equals(key));
        }

        /// <summary>
        /// Adds the specified key and value to the map.
        /// </summary>
        /// <param name="key">Adds the specified key and value to the dictionary.</param>
        /// <param name="value">The value of the element to add. The value can be null for reference types.</param>
        public void Add(TKey key, TValue value)
        {
            var pair = new KeyValuePair<TKey, TValue>(key, value);
            _pairList.Add(pair);
        }

        /// <summary>
        /// Removes the values associated with the specified key from the <see cref="Map&lt;TKey, TValue&gt;"/>.
        /// </summary>
        /// <param name="key">The key of the elements to remove.</param>
        /// <returns>
        /// <c>true</c> if removed successfully; otherwise, <c>false</c>.
        /// </returns>
        public bool Remove(TKey key)
        {
            return (from pair in _pairList where pair.Key.Equals(key) select _pairList.Remove(pair)).FirstOrDefault();
        }

        bool IDictionary<TKey, TValue>.TryGetValue(TKey key, out TValue value)
        {
            foreach (var keyValuePair in _pairList.Where(keyValuePair => keyValuePair.Key.Equals(key)))
            {
                value = keyValuePair.Value;
                return true;
            }
            value = default(TValue);
            return false;
        }

        TValue IDictionary<TKey, TValue>.this[TKey key]
        {
            get
            {
                foreach (var keyValuePair in _pairList.Where(keyValuePair => keyValuePair.Key.Equals(key)))
                {
                    return keyValuePair.Value;
                }
                throw new KeyNotFoundException();
            }
            set
            {
                for (int i = 0; i < _pairList.Count; i++)
                {
                    if (_pairList[i].Key.Equals(key))
                    {
                        _pairList[i] = new KeyValuePair<TKey, TValue>(key, value);
                    }
                }
            }
        }

        /// <summary>
        /// Gets a collection containing the keys in the <see cref="Map&lt;TKey, TValue&gt;"/>.
        /// </summary>
        public ICollection<TKey> Keys
        {
            get { return _pairList.Select(keyValuePair => keyValuePair.Key).Distinct().ToList(); }
        }

        /// <summary>
        /// Gets a collection containing the values in the <see cref="Map&lt;TKey, TValue&gt;"/>.
        /// </summary>
        public ICollection<TValue> Values
        {
            get { return _pairList.Select(keyValuePair => keyValuePair.Value).ToList(); }
        }

        #endregion

        #region IXmlSerializable Members

        XmlSchema IXmlSerializable.GetSchema()
        {
            return null;
        }

        void IXmlSerializable.ReadXml(XmlReader reader)
        {
            var keySerializer = new XmlSerializer(typeof (TKey));
            var valueSerializer = new XmlSerializer(typeof (TValue));

            bool wasEmpty = reader.IsEmptyElement;
            reader.Read();

            if (wasEmpty)
                return;

            while (reader.NodeType != XmlNodeType.EndElement)
            {
                reader.ReadStartElement("item");
                reader.ReadStartElement("key");

                var key = (TKey) keySerializer.Deserialize(reader);
                reader.ReadEndElement();

                reader.ReadStartElement("value");
                var value = (TValue) valueSerializer.Deserialize(reader);
                reader.ReadEndElement();
                Add(key, value);
                reader.ReadEndElement();
                reader.MoveToContent();
            }
            reader.ReadEndElement();
        }

        void IXmlSerializable.WriteXml(XmlWriter writer)
        {
            var keySerializer = new XmlSerializer(typeof (TKey));
            var valueSerializer = new XmlSerializer(typeof (TValue));

            foreach (var item in this)
            {
                writer.WriteStartElement("item");
                writer.WriteStartElement("key");
                keySerializer.Serialize(writer, item.Key);
                writer.WriteEndElement();
                writer.WriteStartElement("value");
                valueSerializer.Serialize(writer, item.Value);
                writer.WriteEndElement();
                writer.WriteEndElement();
            }
        }

        #endregion

        /// <summary>
        /// Gets the values associated with the specified key. If the key has multiple instances then
        /// it will return a collection of all associated values.
        /// </summary>
        /// <param name="key">The key of the values to get.</param>
        /// <returns>
        /// A collection of values associated with the specified key, if the key is found; otherwise, empty collection.
        /// </returns>
        public IEnumerable<TValue> GetValues(TKey key)
        {
            return
                _pairList.Where(keyValuePair => keyValuePair.Key.Equals(key)).Select(keyValuePair => keyValuePair.Value);
        }
    }
}